﻿/*
Copyright (C) 2009  Tomasz Chrzanowski

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>
*/

using System;
using System.Windows.Forms;

namespace Com.TomCdc.ChangeDataCapture
{
    internal class TreeNodeTable:TreeNode
    {
        /// <summary>
        /// The source table being represented by <see cref="TreeNodeTable"/> class.
        /// </summary>
        private SourceTable _sqlSrcTbl;

        /// <summary>
        /// The name of an icon representing standard table (enabled, not changed, with no orphan columns).
        /// </summary>
        private const string ICON_DBTBL_STD = "DbTblStd.ico";

        /// <summary>
        /// The name of an icon representing table with changed tracking column(s).
        /// </summary>
        private const string ICON_DBTBL_CHANGED = "DbTblChanged.ico";

        /// <summary>
        /// The name of an icon representing disabled table (with no primary/uniqe column).
        /// </summary>
        private const string ICON_DBTBL_DISABLED = "DbTblDisabled.ico";

        /// <summary>
        /// The name of an icon representing table with orphan column(s) (columns included in the capture
        /// trigger but deleted from tracked table).
        /// </summary>
        private const string ICON_DBTBL_HAS_ORPHAN = "DbTblHasOrphan.ico";

        private bool _isInstallRequired;

        internal delegate void TableStateChangedHandler(SourceTable table);

        internal event TableStateChangedHandler TableAddedStateChanged;

        internal event TableStateChangedHandler TableRemovedStateChanged;

        /// <summary>
        /// Initializes a new instance of <see cref="TreeNodeTable"/> with the <see cref="SourceTable"/> object.
        /// </summary>
        /// <param name="srcTable">The source table represented by the new <see cref="TreeNodeTable"/> object.</param>
        internal TreeNodeTable(SourceTable srcTable)
        {
            // input argument validation
            if (srcTable == null)
            {
                throw new ArgumentNullException("srcTable");
            }

            _sqlSrcTbl = srcTable;

            // sets node name with source table identifier
            Text = srcTable.TableIdentifier;

            // sets node icon
            SetIcon(ICON_DBTBL_STD);
        }

        internal string TableIdentifier
        {
            get
            {
                return _sqlSrcTbl.TableIdentifier;
            }
        }

        /// <summary>
        /// Gets a value indicating wheather the table can be tracked (has primary or uniqe column).
        /// </summary>
        internal bool IsDisabled
        {
            get
            {
                return _sqlSrcTbl.PrimaryKey == null ? true : false;
            }
        }

        /// <summary>
        /// Gets a value indicating wheather the table is currently tracked.
        /// </summary>
        internal bool IsTracked
        {
            get
            {
                return _sqlSrcTbl.IsTracked;
            }
        }

        /// <summary>
        /// Gets a value indicating wheather any of table columns is marked for tracking activation.
        /// Sets all table columns to be included in tracing activation.
        /// </summary>
        private bool ActivateTracking
        {
            get
            {
                bool activate = false;
                // goes through all columns and checks if any marked for tracking activation
                foreach (TreeNodeColumn col in Nodes)
                {
                    if (col.ActivateTracking)
                    {
                        activate = true;
                        break;
                    }
                }
                return activate;
            }

            set
            {
                // sets all table columns for tracking activation
                ActivateAllColumns(value);
                SetInstallRequiredFlag();
                SetNodeIcon();
            }
        }



        /// <summary>
        /// Gets value indicating whether the table requires installation. 
        /// </summary>
        internal bool IsInstallRequired
        {
            get
            {
                return _isInstallRequired;
            }
        }

        /// <summary>
        /// Sets <see cref="_isInstallRequired"/> flag indicating whether capture trigger installation required.
        /// The check is performed on all columns to identify if any column was included to/excluded from tracking.
        /// The second step checks if trigger contains orphan column(s), 
        /// if yes table tracking has to be rebuild/reinstalled.
        /// </summary>
        public void SetInstallRequiredFlag()
        {
            bool result = false;

            // goes through all column, checking if tracking changed (enabled/disabled)
            foreach (TreeNodeColumn srcCol in Nodes)
            {
                if (srcCol.ActivateTracking != srcCol.IsTracked)
                {
                    result = true;
                    break;
                }
            }

            // additionaly checks if orphan column(s) exist, if yes table tracking has to be rebuild/reinstalled
            bool newState = result || _sqlSrcTbl.HasOrphanTriggerColumn;

            if (newState != _isInstallRequired)
            {
                _isInstallRequired = newState;
                if (newState)
                {
                    OnTableAddedToInstall();
                }
                else
                {
                    OnTableRemovedFromInstall();
                }
            }
        }


        /// <summary>
        /// Sets <see cref="TreeNodeTable"/> checked property if any of its columns is included in tracking (has 
        /// checked property true)
        /// </summary>
        internal void SetNodeCheckedState()
        {
            bool isAnyColChecked = false;
            foreach (TreeNodeColumn node in Nodes)
            {
                if (node.Checked)
                {
                    isAnyColChecked = true;
                    break;
                }
            }
            Checked = isAnyColChecked;
        }

        /// <summary>
        /// Activates (includes) when argument true or deactivates (excludes) when false all table columns tracking 
        /// flag providing a column is not disabled.
        /// </summary>
        /// <param name="activate">The activation state to be set.</param>
        private void ActivateAllColumns(bool activate)
        {
            foreach (TreeNodeColumn node in Nodes)
            {
                if (!node.IsDisabled)
                {
                    node.ActivateTracking = activate;
                }
            }
        }

        /// <summary>
        /// Checks table properties and sets the icon accordingly.
        /// </summary>
        internal void SetNodeIcon()
        {
            // checks if any of table columns have changed tracking activation
            if (IsInstallRequired)
            {
                SetIcon(ICON_DBTBL_CHANGED);
            }
            else
            {
                // no changed tracing activation sets standard icon
                SetIcon(ICON_DBTBL_STD);
            }

            // when current capture trigger has column not existing in the table, mark as orphan (reinstall required)
            if (_sqlSrcTbl.HasOrphanTriggerColumn)
            {
                SetIcon(ICON_DBTBL_HAS_ORPHAN);
            }

            // when no primary key/unique column table tracking disabled
            if (GetTablePrimaryKey() == null)
            {
                SetIcon(ICON_DBTBL_DISABLED);
            }
        }

        /// <summary>
        /// Sets the node icon, provided in the argument. Sets node properties SelectedImageKey and ImageKey, 
        /// thanks to it the same icon is displayed for selected and not selected node.
        /// </summary>
        /// <param name="iconName">The name of an icon existing in <see cref="uxImageList16"/> object.</param>
        private void SetIcon(string iconName)
        {
            // validates input argument
            if (string.IsNullOrEmpty(iconName))
            {
                throw new ArgumentException("The iconName argument must not be empty or null.");
            }

            // the same icon needed when node selected and  not selected
            this.SelectedImageKey = iconName;
            this.ImageKey = iconName;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="treeNodeColumn"></param>
        internal void ColumnStateChanged(TreeNodeColumn treeNodeColumn)
        {
            if (treeNodeColumn == null)
            {
                throw new ArgumentNullException("treeNodeColumn");
            }

            // checks if state change allowed, UI should prevent from going into not allowed state
            // so this is a double check
            if (IsColumnStateChangeAllowed(treeNodeColumn))
            {
                // activate/deactivate column according to node check box 
                treeNodeColumn.ActivateTracking = treeNodeColumn.Checked;
                
                // finds table primary key
                TreeNodeColumn primKey = GetTablePrimaryKey();
                
                // if column could have been checked off, the table is enabled and has primary key, if not,
                // incorrectly UI level validation was performed and exception thrown
                if(primKey == null)
                {
                    throw new InvalidOperationException("Providing current state/type of the table or column," +
                        " changing node checked state is impossible.");
                }

                // activate primary key alway when any other column in table activated, primary key must be included in
                // tracking
                primKey.ActivateTracking = ActivateTracking;
                
                // checks if primary key left as the only tracked column, if so, deactivate it
                if (IsOnlyPrimaryKeyActivate())
                {
                    // deactivate primay key tracking
                    primKey.ActivateTracking = false;
                }

                // cheks if changes which require installation were done
                SetInstallRequiredFlag();

                // sets parent node(the table) check box and icon to reflect change
                SetNodeCheckedState();
                
                // sets node icon to reflect the change
                SetNodeIcon();
            }
            else
            {
                throw new InvalidOperationException("Providing current state/type of the table or column," +
                    " changing node checked state is impossible.");
            }

        }

        /// <summary>
        /// Gets a value that indicates whether the only column marked for tracking activation is primary key.
        /// </summary>
        /// <returns>true if the primary key is the only activated column in the table.</returns>
        private bool IsOnlyPrimaryKeyActivate()
        {
            // goes through all columns in the table
            foreach (TreeNodeColumn col in Nodes)
            {
                // if the column is not primary key and is activated return false
                if (!col.IsPrimaryKey && col.ActivateTracking)
                {
                    return false;
                }
            }
            return true;
        }

        /// <summary>
        /// Gets the value that indicates whether a column can be activated.
        /// </summary>
        /// <param name="colNode">The column being checked.</param>
        /// <returns>true if activation is allowed.</returns>
        internal bool IsColumnStateChangeAllowed(TreeNodeColumn colNode)
        {
            if (colNode == null)
            {
                throw new ArgumentNullException("colNode");
            }

            TreeNodeTable tblNode = (TreeNodeTable)colNode.Parent;
            
            // if table or column disabled or the table hasn't got primary key column can not be activated
            if (colNode.IsDisabled || tblNode.IsDisabled || colNode.IsPrimaryKey)
            {
                return false;
            }
            return true;
        }

        internal bool IsTableStateChangeAllowed(bool toActivateState)
        {
            if (IsDisabled && toActivateState)
            {
                return false;
            }

            return true;
        }

        internal void StateChanged()
        {
            if (IsTableStateChangeAllowed(Checked))
            {
                ActivateTracking = Checked;
            }
            else
            {
                throw new InvalidOperationException("Providing current state/type of the table or column," +
                    " changing node checked state is not allowed.");
            }
        }

        private TreeNodeColumn GetTablePrimaryKey()
        {
            foreach (TreeNodeColumn col in Nodes)
            {
                if (col.IsPrimaryKey) return col;
            }
            return null;
        }

        protected virtual void OnTableAddedToInstall()
        {
            if (TableAddedStateChanged != null)
            {
                TableAddedStateChanged(_sqlSrcTbl);
            }
        }

        protected virtual void OnTableRemovedFromInstall()
        {
            if (TableRemovedStateChanged != null)
            {
                TableRemovedStateChanged(_sqlSrcTbl);
            }
        }
    }
}
